package cate.game.pvp.pointrace;

import cate.common.table.pvp.pointrace.row.PointRaceRobotSideRow;
import cate.common.util.TimeTool;
import cate.game.db.facade.KvLoader;
import cate.game.db.mongo.kv.KvMongo;
import cate.game.framework.ModuleMan;
import cate.game.framework.advanced.AdvancedLongAttributes;
import cate.game.play.part.PlaySide;
import cate.game.pvp.pointrace.data.*;
import cate.game.pvp.pointrace.handler.*;
import cate.game.pvp.pointrace.support.PointRaceGMSupport;
import cate.game.pvp.pointrace.timer.PointRaceTimerTask.Settings;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import easy.java.practice.errorcode.EcResult;
import lombok.Getter;
import org.springframework.util.CollectionUtils;

import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;

import static cate.common.table.d.GDPointRace.WHOLE_SEASON_MILLIS;
import static cate.game.event.pointrace.PointRaceTimerEvent.EventType.*;
import static cate.game.pvp.pointrace.data.PointRacePs.INDEX_NAME;
import static cate.game.pvp.pointrace.data.PointRacePs.INDEX_PLAYER;
import static cate.game.pvp.pointrace.data.PointRacePs.INDEX_ROBOT;
import static cate.game.pvp.pointrace.support.PointRaceGMSupport.RESET_LOCAL_TIME_MASK;

public class PointRaceDataManager extends ModuleMan {
    @Getter
    private PointRaceKV data;

    @Getter
    private final PointRaceRankRwdData rankRwdData = new PointRaceRankRwdData();

    @Getter
    private final PointRaceChallenge challenge = new BothPointRaceChallenge();
    @Getter
    private PointRaceOpQuery opQuery;
    @Getter
    private PointRacePsOperation psOperation;
    @Getter
    private PointRaceRankHandler rankHandler;
    @Getter
    private PointRaceRankRwdOperation rankRwdOperation;
    @Getter
    private PointRaceExcelQueryOperation excelQueryOperation;

    @Override
    public void initDependent() throws Exception {
        data = new KvLoader<>(game, PointRaceKV.class, KvMongo.KEY_POINT_RACE).getBySrvId();

        buildKVData(data);

        buildPsData(data);

        buildRankData(data);

        if (Objects.isNull(data.getDailyRankRd())) {
            data.setDailyRankRd(Maps.newConcurrentMap());
        }

        this.opQuery = new BothPointRaceOpQuery(this.getData()::getPsData);
        this.psOperation = new BothPointRacePsOperation(this.getData()::getPsData);
        this.rankHandler = new BothPointRaceRankHandler(
                this.getData()::getRankData,
                psOperation,
                (srvId, uid, rankId) -> game.rank.handleAdmire(uid, rankId)
        );
        this.rankRwdOperation = new BothPointRaceRankRwdOperation(this::getRankRwdData);
        this.excelQueryOperation = new BothPointRaceExcelQueryOperation(() -> game.table);
    }

    private void buildRankData(PointRaceKV data) {
        if (Objects.isNull(data.getRankData())) {
            data.setRankData(new PointRaceRankData());
        }

        data.getRankData()
                .getValueList()
                .forEach(this::buildRankDataIndex);
    }

    private void buildRankDataIndex(PointRaceRank rank) {
        data.getRankData().addIndex(PointRaceRank.INDEX_NAME, rank.getIndexName(), rank);
    }

    public void gmRebuildPsData() {
        data.getPsData().clear();
        buildPsData(data);
    }

    private void buildPsData(PointRaceKV data) {
        if (Objects.isNull(data.getPsData())) {
            data.setPsData(new PointRacePsData());
        }

        if (data.getPsData().getValueMap().isEmpty()) {
            for (PointRaceRobotSideRow row : game.table.pointRace.robotSide.getList()) {
                EcResult<PlaySide> r = game.fight.side.createPlaySideByCode(row.instance);
                if (r.ok() && Objects.nonNull(r.data)) {
                    PlaySide ps = r.data;
                    ps.snapshot.activeTime = 0;
                    EcResult<PointRacePs> psr = PointRacePs.compressEc(r.data, row.points);
                    if (psr.ok()) {
                        psr.data.getOpponent().setRid(row.id);
                        psr.data.getOpponent().getSnapshot().unreal = true;
                        data.getPsData().addValue(psr.data.getIndexName(), psr.data);
                        data.getPsData().addIndex(INDEX_NAME, psr.data.getIndexName(), psr.data);
                        data.getPsData().addIndex(INDEX_ROBOT, psr.data.getIndexRobotName(), psr.data);
                    }
                }
            }
        }

        data.getPsData()
                .getValueMap()
                .values()
                .forEach(this::buildPsDataIndex);
    }

    private void buildKVData(PointRaceKV data) {
        if (Objects.nonNull(data.getGmSupport())) {
            AdvancedLongAttributes attributes = data.getGmSupport().getAttributes();
            if (Objects.nonNull(attributes) && attributes.hasAttribute(RESET_LOCAL_TIME_MASK)) {
                if (Objects.nonNull(data.getTimerSettings())) {
                    data.getTimerSettings().clear();
                }
                attributes.removeAttribute(RESET_LOCAL_TIME_MASK);
            }
        }

        if (CollectionUtils.isEmpty(data.getTimerSettings())) {
            long firstOpenTime = game.getOpenTime();
            data.setDay(0);
            data.setSeason(0);
            data.setCycleTimeMs(WHOLE_SEASON_MILLIS);
            data.setOpenTime(firstOpenTime);
            data.setClosedTime(firstOpenTime + WHOLE_SEASON_MILLIS);
            data.setTimerSettings(Lists.newArrayList(
                    Settings.of(0, TimeTool.calTimeStamp(firstOpenTime, 1), OPEN),
                    Settings.of(1, TimeTool.calTimeStamp(firstOpenTime, 1), DAY_SPAN),
                    Settings.of(1, TimeTool.calTimeStamp(firstOpenTime, 1, "21:00:00"), RANK_DAY_RWD),
                    Settings.of(2, TimeTool.calTimeStamp(firstOpenTime, 2), DAY_SPAN),
                    Settings.of(2, TimeTool.calTimeStamp(firstOpenTime, 2, "21:00:00"), RANK_DAY_RWD),
                    Settings.of(3, TimeTool.calTimeStamp(firstOpenTime, 3), DAY_SPAN),
                    Settings.of(3, TimeTool.calTimeStamp(firstOpenTime, 3, "21:00:00"), RANK_DAY_RWD),
                    Settings.of(4, TimeTool.calTimeStamp(firstOpenTime, 4), DAY_SPAN),
                    Settings.of(4, TimeTool.calTimeStamp(firstOpenTime, 4, "21:00:00"), RANK_DAY_RWD),
                    Settings.of(5, TimeTool.calTimeStamp(firstOpenTime, 5), DAY_SPAN),
                    Settings.of(5, TimeTool.calTimeStamp(firstOpenTime, 5, "21:00:00"), RANK_DAY_RWD),
                    Settings.of(6, TimeTool.calTimeStamp(firstOpenTime, 6), DAY_SPAN),
                    Settings.of(6, TimeTool.calTimeStamp(firstOpenTime, 6, "21:00:00"), RANK_DAY_RWD),
                    Settings.of(7, TimeTool.calTimeStamp(firstOpenTime, 7), DAY_SPAN),
                    Settings.of(7, TimeTool.calTimeStamp(firstOpenTime, 7, "21:00:00"), RANK_DAY_RWD),
                    Settings.of(7, TimeTool.calTimeStamp(firstOpenTime, 7, "23:00:00"), RANK_SEASON_RWD),
                    Settings.of(7, TimeTool.calTimeStamp(firstOpenTime, 8), SWITCH)));
            if (Objects.isNull(data.getRecords())) {
                data.setRecords(new CopyOnWriteArrayList<>());
            }
        }

        if (Objects.isNull(data.getAttributes())) {
            data.setAttributes(AdvancedLongAttributes.of(0L));
        }

        if (Objects.isNull(data.getEventAttributes())) {
            data.setEventAttributes(AdvancedLongAttributes.of(0L));
        }

        if (Objects.isNull(data.getGmSupport())) {
            data.setGmSupport(new PointRaceGMSupport());
            PointRaceGMSupport gmSupport = data.getGmSupport();
            if (Objects.isNull(gmSupport.getAttributes())) {
                gmSupport.setAttributes(AdvancedLongAttributes.of(0L));
            }
        }
    }

    private void buildPsDataIndex(PointRacePs ps) {
        data.getPsData().addIndex(INDEX_NAME, ps.getIndexName(), ps);
        data.getPsData().addIndex(
                ps.getOpponent().getSnapshot().unreal
                        ? INDEX_ROBOT
                        : INDEX_PLAYER,
                ps.getOpponent().getSnapshot().unreal
                        ? ps.getIndexRobotName()
                        : ps.getIndexName(),
                ps);
    }

    public void clear() {
        rankRwdData.clear();
        data.getRankData().clear();
        data.getDailyRankRd().clear();
        rankHandler.clear();
    }

    @Override
    public void shutdown() {
        super.shutdown();
        save();
    }

    public void save() {
        game.db.kv.save(data);
    }
}
